/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the APACHE.txt file.  */
package org.jahia.sqlprofiler.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Properties;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import org.apache.log4j.Category;
import org.apache.log4j.PropertyConfigurator;
import javax.swing.*;
import java.awt.FlowLayout;
import java.awt.Paint;
import java.awt.Color;
import org.jCharts.properties.PieChart2DProperties;
import org.jCharts.chartData.PieChartDataSet;
import org.jCharts.nonAxisChart.PieChart2D;
import org.jCharts.properties.LegendProperties;
import org.jCharts.properties.ChartProperties;
import java.awt.Graphics2D;

/**
 * The main application.
 *
 * @author <a href="mailto:oliver@puppycrawl.com">Oliver Burn</a>
 *
 * 11/14/2003 : Add DML statements by Jean-Philippe VALENTIN (JPV) version 0.3
 */
public class Main
    extends JFrame
{
    /** the default port number to listen on **/
    private static final int DEFAULT_PORT = 4445;

    /** name of property for port name **/
    public static final String PORT_PROP_NAME = "chainsaw.port";

    /** use to log messages **/
    private static final Category LOG = Category.getInstance(Main.class);


    /**
     * Creates a new <code>Main</code> instance.
     */
    private Main() {
        super("SQL Profiler v0.3");  //JPV : change version 0.2 to 0.3

        // First let's use the system's default look and feel
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (UnsupportedLookAndFeelException ulafe) {
            LOG.error("Unsupported look & feel : ", ulafe);
        } catch (IllegalAccessException iae) {
            LOG.error("Illegal access exception : ", iae);
        } catch (InstantiationException ie) {
            LOG.error("Error : ", ie);
        } catch (ClassNotFoundException cnfe) {
            LOG.error("Error : ", cnfe);
        }

        // create the all important model
        final LoggerTableModel loggerModel = new LoggerTableModel();
        final ProfileStatementTableModel profileStatementTableModel = new
            ProfileStatementTableModel(loggerModel);
        loggerModel.setProfileStatementModel(profileStatementTableModel);

        //Create the menu bar.
        final JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);
        final JMenu menu = new JMenu("File");
        menuBar.add(menu);

        /*
        try {
            final LoadXMLAction lxa = new LoadXMLAction(this, loggerModel);
            final JMenuItem loadMenuItem = new JMenuItem("Load Log4J XML file...");
            menu.add(loadMenuItem);
            loadMenuItem.addActionListener(lxa);
        } catch (NoClassDefFoundError e) {
            LOG.info("Missing classes for XML parser", e);
            JOptionPane.showMessageDialog(
                this,
                "XML parser not in classpath - unable to load XML events.",
                "CHAINSAW",
                JOptionPane.ERROR_MESSAGE);
        } catch (Exception e) {
            LOG.info("Unable to create the action to load XML files", e);
            JOptionPane.showMessageDialog(
                this,
                "Unable to create a XML parser - unable to load XML events.",
                "CHAINSAW",
                JOptionPane.ERROR_MESSAGE);
        }
        */

        final JMenuItem exitMenuItem = new JMenuItem("Exit");
        menu.add(exitMenuItem);
        exitMenuItem.addActionListener(ExitAction.INSTANCE);

        final MainTabbedPanel mainTabbedPanel = new MainTabbedPanel();

        final JPanel loggerPanel = new JPanel(new BorderLayout());
        final JPanel profilerPanel = new JPanel(new BorderLayout());
        final JPanel analysisPanel = new JPanel(new BorderLayout());

        final ToolbarPanel toolbarPanel = new ToolbarPanel(loggerModel, profileStatementTableModel);
        getContentPane().add(toolbarPanel, BorderLayout.NORTH);

        buildLoggerPanel(loggerModel, loggerPanel);

        buildProfilePanel(profileStatementTableModel, profilerPanel);

        buildAnalysisPanel(loggerModel, profileStatementTableModel, analysisPanel);

        mainTabbedPanel.addTab("Profiler", profilerPanel, "SQL Profiler");
        mainTabbedPanel.addTab("Logger", loggerPanel, "Logging view");
        mainTabbedPanel.addTab("Analysis", analysisPanel, "Analysis view");
        mainTabbedPanel.finishTabs();
        getContentPane().add(mainTabbedPanel, BorderLayout.CENTER);

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent aEvent) {
                ExitAction.INSTANCE.actionPerformed(null);
            }
        });

        Box statusBar = Box.createHorizontalBox();
        JPanel messagePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
        messagePanel.setBorder(BorderFactory.createLoweredBevelBorder());
        JLabel messageTextLabel = new JLabel();
        messageTextLabel.setHorizontalAlignment(JLabel.LEFT);
        messageTextLabel.setText("Not connected to P6Spy");
        messagePanel.add(messageTextLabel);
        statusBar.add(messagePanel);
        /*
        JPanel progressPanel = new JPanel(new BorderLayout());
        progressPanel.setBorder(BorderFactory.createLoweredBevelBorder());
        JProgressBar progressBar = new JProgressBar();
        progressBar.setValue(0);
        progressPanel.add(progressBar, BorderLayout.CENTER);
        statusBar.add(progressPanel);
        */
        Box statusLabelBox = Box.createHorizontalBox();
        StatusLabel statusLabel = new StatusLabel();
        statusLabelBox.add(statusLabel);
        statusBar.add(statusLabelBox);
        getContentPane().add(statusBar, BorderLayout.SOUTH);

        pack();
        setVisible(true);

        setupReceiver(loggerModel, statusLabel, messageTextLabel);
    }

    private void buildProfilePanel(ProfileStatementTableModel profileStatementTableModel,
                                   JPanel profilerPanel) {
        // Create the table
        final JTable profileStatementTable = new JTable(profileStatementTableModel);
        profileStatementTableModel.addMouseListenerToHeaderInTable(profileStatementTable);
        profileStatementTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        profileStatementTable.getColumnModel().getColumn(0).setMinWidth(120);
        profileStatementTable.getColumnModel().getColumn(1).setMaxWidth(70);
        profileStatementTable.getColumnModel().getColumn(2).setMaxWidth(30);
        profileStatementTable.getColumnModel().getColumn(3).setPreferredWidth(70);
        profileStatementTable.getColumnModel().getColumn(4).setPreferredWidth(70);
        profileStatementTable.getColumnModel().getColumn(5).setPreferredWidth(680-70-70-30-70-150);
        final JScrollPane profileScrollPane = new JScrollPane(profileStatementTable);
        profileScrollPane.setBorder(BorderFactory.createTitledBorder("SQL Statements: "));
        profileScrollPane.setPreferredSize(new Dimension(700, 150));

        ProfileResultTableModel profileResultModel = new ProfileResultTableModel();
        profileStatementTableModel.setProfileResultModel(profileResultModel);
        final JTable profileResultTable = new JTable(profileResultModel);
        profileResultModel.addMouseListenerToHeaderInTable(profileResultTable);
        profileResultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        profileResultTable.getColumnModel().getColumn(0).setMaxWidth(50);
        profileResultTable.getColumnModel().getColumn(1).setMaxWidth(70);
        profileResultTable.getColumnModel().getColumn(2).setMaxWidth(70);
        profileResultTable.getColumnModel().getColumn(3).setPreferredWidth(160);
        profileResultTable.getColumnModel().getColumn(4).setPreferredWidth(680-160-70-70-50);
        final JScrollPane profileResultScrollPane = new JScrollPane(profileResultTable);
        profileResultScrollPane.setBorder(BorderFactory.createTitledBorder("Profile results: "));
        profileResultScrollPane.setPreferredSize(new Dimension(700, 150));

        Box counterBox = Box.createHorizontalBox();

        JLabel statementCountTextLabel = new JLabel(" Total statements : ");
        counterBox.add(statementCountTextLabel);
        JLabel statementCountValueLabel = new JLabel("0");
        profileStatementTableModel.setStatementCountValueLabel(statementCountValueLabel);
        counterBox.add(statementCountValueLabel);
        counterBox.add(Box.createHorizontalStrut(10));
        JLabel statementTimeTextLabel = new JLabel(" Total statement time [ms] : ");
        counterBox.add(statementTimeTextLabel);
        JLabel statementTimeValueLabel = new JLabel("0");
        profileStatementTableModel.setStatementTimeValueLabel(statementTimeValueLabel);
        counterBox.add(statementTimeValueLabel);

        profilerPanel.add(counterBox, BorderLayout.NORTH);

        // Create the details
        final JPanel profileDetails = new ProfileDetailsPanel(profileStatementTable, profileStatementTableModel);
        profileDetails.setPreferredSize(new Dimension(700, 150));

        final JSplitPane profileSplitPane =
            new JSplitPane(JSplitPane.VERTICAL_SPLIT, profileScrollPane, profileResultScrollPane);

        final JSplitPane profileSplitPane2 =
            new JSplitPane(JSplitPane.VERTICAL_SPLIT, profileSplitPane, profileDetails);

        profilerPanel.add(profileSplitPane2, BorderLayout.CENTER);

    }

    private void buildLoggerPanel(LoggerTableModel loggerModel,
                                  JPanel loggerPanel) {
        // Add control panel
        final ControlPanel cp = new ControlPanel(loggerModel);
        loggerPanel.add(cp, BorderLayout.NORTH);

        // Create the table
        final JTable loggerTable = new JTable(loggerModel);
        loggerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        final JScrollPane loggerScrollPane = new JScrollPane(loggerTable);
        loggerScrollPane.setBorder(BorderFactory.createTitledBorder("Events: "));
        loggerScrollPane.setPreferredSize(new Dimension(700, 150));

        // Create the details
        final JPanel loggerDetails = new DetailPanel(loggerTable, loggerModel);
        loggerDetails.setPreferredSize(new Dimension(700, 150));

        // Add the table and stack trace into a splitter
        final JSplitPane loggerSplitPane =
            new JSplitPane(JSplitPane.VERTICAL_SPLIT, loggerScrollPane, loggerDetails);
        loggerPanel.add(loggerSplitPane, BorderLayout.CENTER);
    }

    private void buildAnalysisPanel(LoggerTableModel loggerModel, ProfileStatementTableModel profileStatementModel, JPanel analysisPanel) {

        try {
            QueryCountChartModel queryCountChartModel = new QueryCountChartModel(profileStatementModel);
            ChartPanel queryCountChartPanel = new ChartPanel(queryCountChartModel);
            loggerModel.setQueryCountChartModel(queryCountChartModel);
            queryCountChartPanel.setPreferredSize(new Dimension(700, 150));

            QueryTrafficChartModel queryTrafficChartModel = new QueryTrafficChartModel(profileStatementModel);
            ChartPanel queryTrafficChartPanel = new ChartPanel(queryTrafficChartModel);
            loggerModel.setQueryTrafficChartModel(queryTrafficChartModel);
            queryTrafficChartPanel.setPreferredSize(new Dimension(700, 150));

            final JSplitPane analysisSplitPane =
                new JSplitPane(JSplitPane.VERTICAL_SPLIT, queryCountChartPanel, queryTrafficChartPanel);

            analysisPanel.add(analysisSplitPane, BorderLayout.CENTER);

        } catch (Throwable t) {
            LOG.error("Error while creating chart panel", t);
        }

    }

    /**
     * Setup recieving messages.
     *
     * @param aModel a <code>MyTableModel</code> value
     */
    private void setupReceiver(final LoggerTableModel aModel,
                               final StatusLabel statusLabel,
                               final JLabel messageTextLabel) {
        int port = DEFAULT_PORT;
        final String strRep = System.getProperty(PORT_PROP_NAME);
        if (strRep != null) {
            try {
                port = Integer.parseInt(strRep);
            } catch (NumberFormatException nfe) {
                LOG.fatal("Unable to parse " + PORT_PROP_NAME +
                          " property with value " + strRep + ".");
                JOptionPane.showMessageDialog(
                    this,
                    "Unable to parse port number from '" + strRep +
                    "', quitting.",
                    "CHAINSAW",
                    JOptionPane.ERROR_MESSAGE);
                System.exit(1);
            }
        }

        try {
            final LoggingReceiver lr = new LoggingReceiver(aModel, port);
            lr.addLogReceptionListener(new LogReceptionAdapter() {
                public void logReceptionConnected(LogReceptionEvent logReceptionEvent) {
                    statusLabel.setStatusActivated(true);
                    messageTextLabel.setText("Connected to P6Spy");
                }
                public void logReceptionDisconnected(LogReceptionEvent logReceptionEvent) {
                    statusLabel.setStatusActivated(false);
                    messageTextLabel.setText("Disconnected from P6Spy");
                }
                public void logReceptionDataReceived(LogReceptionEvent logReceptionEvent) {
                    aModel.addEvent(logReceptionEvent.getEventDetails());
                }
            });

            lr.start();
        } catch (IOException e) {
            LOG.fatal("Unable to connect to socket server, quiting", e);
            JOptionPane.showMessageDialog(
                this,
                "Unable to create socket on port " + port + ", quitting.",
                "CHAINSAW",
                JOptionPane.ERROR_MESSAGE);
            System.exit(1);
        }
    }


    ////////////////////////////////////////////////////////////////////////////
    // static methods
    ////////////////////////////////////////////////////////////////////////////


    /** initialise log4j **/
    private static void initLog4J() {
        final Properties props = new Properties();
        props.setProperty("log4j.rootCategory", "DEBUG, A1");
        props.setProperty("log4j.appender.A1",
                          "org.apache.log4j.ConsoleAppender");
        props.setProperty("log4j.appender.A1.layout",
                          "org.apache.log4j.TTCCLayout");
        PropertyConfigurator.configure(props);
    }

    /**
     * The main method.
     *
     * @param aArgs ignored
     */
    public static void main(String[] aArgs) {
        initLog4J();
        new Main();
    }
}
